/*
 * Copyright (C) 2014 Lucid Wolf
 * Copyright 1996 by Darius Bacon
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package com.lucidhex.equation.parser;

import com.lucidhex.equation.parser.expr.LiteralExpr;
import com.lucidhex.equation.parser.expr.UnaryExpr;

/**
 * Started with Darius Bacons original code heavy modification to remove 
 * static dependencies and overall cleanup including reference to variables
 * @author Lucid Wolf
 */
// Mathematical expressions.
// Copyright 1996 by Darius Bacon; see the file COPYING.


/**
 * A mathematical expression, built out of literal numbers, variables,
 * arithmetic and relational operators, and elementary functions.  It
 * can be evaluated to get its value given its variables' current
 * values.  The operator names are from java.lang.Math where possible.
 */
public abstract class Expr {

    /** Calculate the expression's value.
     * @return the value given the current variable values */
    public abstract double value();

    /** Binary operator: addition        */  public static final int ADD =  0;  
    /** Binary operator: subtraction     */  public static final int SUB =  1;
    /** Binary operator: multiplication  */  public static final int MUL =  2;
    /** Binary operator: division        */  public static final int DIV =  3;
    /** Binary operator: exponentiation  */  public static final int POW =  4;
    /** Binary operator: arctangent      */  public static final int ATAN2 = 5;
    /** Binary operator: maximum         */  public static final int MAX =  6;
    /** Binary operator: minimum         */  public static final int MIN =  7;
    /** Binary operator: less than       */  public static final int LT  =  8;
    /** Binary operator: less or equal   */  public static final int LE  =  9;
    /** Binary operator: equality        */  public static final int EQ  = 10;
    /** Binary operator: inequality      */  public static final int NE  = 11;
    /** Binary operator: greater or equal*/  public static final int GE  = 12;
    /** Binary operator: greater than    */  public static final int GT  = 13;
    /** Binary operator: logical and     */  public static final int AND = 14;
    /** Binary operator: logical or      */  public static final int OR  = 15;
  
    /** Unary operator: absolute value*/   public static final int ABS   = 100;
    /** Unary operator: arccosine */       public static final int ACOS  = 101;
    /** Unary operator: arcsine   */       public static final int ASIN  = 102;
    /** Unary operator: arctangent*/       public static final int ATAN  = 103;
    /** Unary operator: ceiling   */       public static final int CEIL  = 104;
    /** Unary operator: cosine    */       public static final int COS   = 105;
    /** Unary operator: e to the x*/       public static final int EXP   = 106;
    /** Unary operator: floor     */       public static final int FLOOR = 107;
    /** Unary operator: natural log*/      public static final int LOG   = 108;
    /** Unary operator: negation        */ public static final int NEG   = 109;
    /** Unary operator: rounding  */       public static final int ROUND = 110;
    /** Unary operator: sine      */       public static final int SIN   = 111;
    /** Unary operator: square root */     public static final int SQRT  = 112;
    /** Unary operator: tangent */         public static final int TAN   = 113;

    /** Make a literal expression.
     * @param v the constant value of the expression
     * @return an expression whose value is always v */
    public static Expr makeLiteral(double v) { 
	return new LiteralExpr(v); 
    }
    /** Make an expression that applies a unary operator to an operand.
     * @param rator a code for a unary operator
     * @param rand operand
     * @return an expression meaning rator(rand)
     */
    public static Expr makeApp1(int rator, Expr rand) {
	Expr app = new UnaryExpr(rator, rand);
	return rand instanceof LiteralExpr
	    ? new LiteralExpr(app.value()) 
	    : app;
    }
    /** Make an expression that applies a binary operator to two operands.
     * @param rator a code for a binary operator
     * @param rand0 left operand
     * @param rand1 right operand
     * @return an expression meaning rator(rand0, rand1)
     */
    public static Expr makeApp2(int rator, Expr rand0, Expr rand1) {
	Expr app = new BinaryExpr(rator, rand0, rand1);
	return rand0 instanceof LiteralExpr && rand1 instanceof LiteralExpr
	    ? new LiteralExpr(app.value()) 
	    : app;
    }
    /** Make a conditional expression.
     * @param test `if' part
     * @param consequent `then' part
     * @param alternative `else' part
     * @return an expression meaning `if test, then consequent, else
     *         alternative' 
     */
    public static Expr makeIfThenElse(Expr test,
				      Expr consequent,
				      Expr alternative) {
	Expr cond = new ConditionalExpr(test, consequent, alternative);
	if (test instanceof LiteralExpr)
            return test.value() != 0 ? consequent : alternative;
	else
	    return cond;
    }
}

class BinaryExpr extends Expr {
    int rator;
    Expr rand0, rand1;

    BinaryExpr(int rator, Expr rand0, Expr rand1) {
	this.rator = rator;
	this.rand0 = rand0;
	this.rand1 = rand1;
    }
    public double value() {
	double arg0 = rand0.value();
	double arg1 = rand1.value();
	switch (rator) {
	case ADD:   return arg0 + arg1;
	case SUB:   return arg0 - arg1;
	case MUL:   return arg0 * arg1;
	case DIV:   return arg0 / arg1; // division by 0 has IEEE 754 behavior
	case POW:   return Math.pow(arg0, arg1);
	case ATAN2: return Math.atan2(arg0, arg1);
	case MAX:   return arg0 < arg1 ? arg1 : arg0;
	case MIN:   return arg0 < arg1 ? arg0 : arg1;
        case LT:    return arg0 <  arg1 ? 1.0 : 0.0;
        case LE:    return arg0 <= arg1 ? 1.0 : 0.0;
        case EQ:    return arg0 == arg1 ? 1.0 : 0.0;
        case NE:    return arg0 != arg1 ? 1.0 : 0.0;
        case GE:    return arg0 >= arg1 ? 1.0 : 0.0;
        case GT:    return arg0  > arg1 ? 1.0 : 0.0;
        case AND:   return arg0 != 0 && arg1 != 0 ? 1.0 : 0.0;
        case OR:    return arg0 != 0 || arg1 != 0 ? 1.0 : 0.0; 
	default: throw new RuntimeException("BUG: bad rator");
	}
    }
}

class ConditionalExpr extends Expr {
    Expr test, consequent, alternative;

    ConditionalExpr(Expr test, Expr consequent, Expr alternative) {
	this.test = test;
	this.consequent = consequent;
	this.alternative = alternative;
    }

    public double value() {
	return test.value() != 0 ? consequent.value() : alternative.value();
    }
}